"""
This module contains the interface for building plugins (ConduitPlugin class) and
some builtin plugins.
"""
import time
import copy
from datetime import datetime
from string import Template
import django.forms as forms
from django.conf import settings
from django.conf.urls.defaults import url
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponse
from instafeed.utils import CustomAsUlFormMixin
from instafeed.utils import reduceLink, unescape
from instafeed.conduits.plugins import ConduitPlugin
from socialauth.lib.facebook import talk_to_fb, get_facebook_signature
from socialauth.models import FacebookUserProfile


class FacebookError(RuntimeError):
    pass


def my_talk_to_fb(method, **kargs):
    params = {
        'method': method,
        'api_key': settings.FACEBOOK_API_KEY,
        'call_id': time.time(),
        'v': '1.0',
        'format': 'json',
        }
    params.update(kargs)
    return talk_to_fb(settings.FACEBOOK_API_KEY, settings.FACEBOOK_API_SECRET, params)


class FacebookStatusConduit(ConduitPlugin):
    """
    Updates facebook status based on feeds updates.
    """

    key = "facebook_status"
    name = "Facebook Status"

    template = "conduits/facebook_status.html"

    def __init__(self, messageFormat="auto"):
        self.uid = None
        self.messageFormat = messageFormat

    def _makeMessage(self, subscription, feed):
        if self.messageFormat == "auto":
            messageFormatStr = "$description"
        else:
            messageFormatStr = self.messageFormat
        messageParams = {
            'subcriptionName': subscription.name,
            'title': unescape(feed.title),
            'link': feed.link,
            'description': unescape(feed.description),
        }
        messageFormat = Template(messageFormatStr)
        message = messageFormat.safe_substitute(messageParams)
        if len(message) > 255:
            messageParams["link"] = reduceLink(messageParams["link"])
            message = messageFormat.safe_substitute(messageParams)
        if len(message) > 255 and not "$link" in messageFormatStr:
            # If message exceeds the size, we will add link to the message
            messageFormatStr += " - $link"
            messageFormat = Template(messageFormatStr)
            message = messageFormat.safe_substitute(messageParams)
        if len(message) > 255:
            maxSize = len(messageParams["description"]) - (len(message) + len(" ...") - 255)
            messageParams["description"] = ' '.join(messageParams["description"][:maxSize + 1].split(' ')[0:-1])
            message = messageFormat.safe_substitute(messageParams)
        if len(message) > 255:
            maxSize = len(messageParams["title"]) - (len(message) + len(" ...") - 255)
            messageParams["title"] = ' '.join(messageParams["title"][:maxSize + 1].split(' ')[0:-1])
            message = messageFormat.safe_substitute(messageParams)
        return message[:255]

    def sendFeed(self, subscription, feed):
        message = self._makeMessage(subscription, feed)
        uid = FacebookUserProfile.objects.get(user=subscription.user).uid
        params = {
            'method': 'users.setStatus',
            'api_key': settings.FACEBOOK_API_KEY,
            'call_id': time.time(),
            'v': '1.0',
            'format': 'json',
            'status': message,
            'uid': uid,
        }
        ret = my_talk_to_fb("stream.publish", message=message, uid=uid)
#        ret = my_talk_to_fb("users.setStatus", status=message, uid=uid)
        if not ret is True:
            raise FacebookError("Error updating status: %s (%s)" % (ret["error_message"],
                ret["error_code"]))
        return ret

    class Form(CustomAsUlFormMixin, forms.Form):
        messageFormat = forms.CharField(initial="auto",
            help_text="Format of the message posted.\n"
                "Use 'auto' for posting feed text when it fits 255 chars, else $title - $link.\n"
                "Also you can use a custom message format, using variables $title, $link, $description,"
                " $subscriptionName")

        def clean(self):
            user = self.data["user"]
            cleaned_data = super(self.__class__, self).clean()
            try:
                uid = FacebookUserProfile.objects.get(user=user).uid
            except FacebookUserProfile.DoesNotExist:
                raise forms.ValidationError("You need to connect your instafeed user to your facebook "
                    "account to use this plugin")

            ret = my_talk_to_fb("users.hasAppPermission", uid=uid, ext_perm="offline_access")
            if ret not in (True, False):
                raise forms.ValidationError("Error checking for permissions: %(errorMessage)s "
                    "(%(error_code)s)" % ret)
            elif not ret:
                raise forms.ValidationError("You need to grant offline access permission to our "
                    "application to use this plugin")

            ret = my_talk_to_fb("users.hasAppPermission", uid=uid, ext_perm="status_update")
            if ret not in (True, False):
                raise forms.ValidationError("Error checking for permissions: %(errorMessage)s "
                    "(%(error_code)s)" % ret)
            elif not ret:
                raise forms.ValidationError("You need to grant status update permission to our "
                    "application to use this plugin")
            return cleaned_data

    @classmethod
    def update_template_params(klass, request, params):
        # Tests if the user connected his FB account
        # and enabled required permissions
        try:
            uid = FacebookUserProfile.objects.get(user=request.user).uid
        except FacebookUserProfile.DoesNotExist:
            params["facebook_status_user_connected"] = False
            params["facebook_status_perm_ok"] = False
        else:
            params["facebook_status_user_connected"] = True
            offline_access = my_talk_to_fb("users.hasAppPermission", uid=uid, ext_perm="offline_access")
            status_update = my_talk_to_fb("users.hasAppPermission", uid=uid, ext_perm="status_update")
            if offline_access == True and status_update == True:
                params["facebook_status_perm_ok"] = True
            else:
                params["facebook_status_perm_ok"] = False
        if not "facebook_status_form" in params:
            params["facebook_status_form"] = klass.Form(prefix=klass.key)
        params["facebook_status_api_key"] = settings.FACEBOOK_API_KEY

    @classmethod
    def validate_new(klass, request):
        formData = copy.copy(request.POST)
        formData["user"] = request.user
        form = klass.Form(formData, prefix=klass.key)

        if form.is_valid():
            return True, {'messageFormat': form.cleaned_data["messageFormat"]}
        else:
            return False, {'facebook_status_form': form}

    @classmethod
    def connect_done_view(klass, request):
        API_KEY = settings.FACEBOOK_API_KEY
        API_SECRET = settings.FACEBOOK_API_SECRET
        REST_SERVER = 'http://api.facebook.com/restserver.php'
        # FB Connect will set a cookie with a key == FB App API Key if the user has been authenticated
        if API_KEY in request.COOKIES:
            signature_hash = get_facebook_signature(API_KEY, API_SECRET, request.COOKIES, True)
            # The hash of the values in the cookie to make sure they're not forged
            # AND If session hasn't expired
            if signature_hash == request.COOKIES[API_KEY] and \
                (request.COOKIES[API_KEY + '_expires'] == '0' or \
                datetime.fromtimestamp(float(request.COOKIES[API_KEY + '_expires'])) > datetime.now()):

                #Log the user in now.
                uid = request.COOKIES[API_KEY + "_user"]

                #Delete cookies
                del request.COOKIES[API_KEY]
                del request.COOKIES[API_KEY + '_session_key']
                del request.COOKIES[API_KEY + '_user']

                try:
                    profile = FacebookUserProfile.objects.get(uid=uid)
                except FacebookUserProfile.DoesNotExist:
                    # Create FacebookUserProfile
                    FacebookUserProfile(user=request.user, uid=uid).save()
                else:
                    if profile.user != request.user:
                        return HttpResponse("Sorry this facebook user it's linked with another account."
                            "Please re-login to this site with your Facebook Account")

                # Redirect back to new_conduit_page
                return HttpResponseRedirect(reverse('instafeed_new_conduit') + "?plugin=" + klass.key)
        return HttpResponse("API_KEY not in cookies")

    @classmethod
    def get_urls(klass):
        return [
            url(r"^%s/connect_done$" % klass.key, klass.connect_done_view,
                name="facebook_status_connect_done"),
            ]
